/*
 * virtio_net.c
 *
 *  Created on: Jan 2, 2022
 *      Author: gewenbin
 */
#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
#include <stdlib.h>
#include <string.h>
#include <linux/compat.h>
#include <net/if_ether.h>
#include <netdev.h>
#include <linux/virtio.h>
#include <linux/virtio_ring.h>
#include <uapi/linux/virtio_net.h>

#define VIRTIO_NET_NUM_RX_BUFS  32

/*
 * This value comes from the VirtIO spec: 1500 for maximum packet size,
 * 14 for the Ethernet header, 12 for virtio_net_hdr. In total 1526 bytes.
 */
#define VIRTIO_NET_RX_BUF_SIZE  1526

struct virtio_net_vq {
    struct virtqueue *vq;

    #define VQ_NAME_LEN        16
    char name[VQ_NAME_LEN];
};

struct virtnet_req {
    union {
        struct virtio_net_hdr vnet_hdr_old;
        struct virtio_net_hdr_mrg_rxbuf vnet_hdr_new;
    };
};

struct virtio_net {
    /* sylixos stuff */
    netdev_t              vnet_dev;
    LW_OBJECT_HANDLE      vnet_txpost;
    LW_OBJECT_HANDLE      vnet_lock;

    struct virtio_device *vdev;

    /* 2 vqs, 1 for rx, 1 for tx */
    struct virtio_net_vq *vqs;

    /* used by tx */
    struct virtnet_req   *req;
    int                   vnet_hdr_len;

    int                   rx_inited;

    void                 *rxbuffer;
    void                 *txbuffer;
};

static void virtnet_tx_done(struct virtqueue *vq)
{
    struct virtio_net *vnet = vq->vdev->priv;
    unsigned int len;

    API_SemaphoreMPend(vnet->vnet_lock, LW_OPTION_WAIT_INFINITE);

    virtqueue_disable_cb(vq);
    while(virtqueue_get_buf(vq, &len));
    if (vq->num_free > 0) {
        // wakeup threads pend on tx
        API_SemaphoreBPost(vnet->vnet_txpost);
    }
    virtqueue_enable_cb(vq);

    API_SemaphoreMPost(vnet->vnet_lock);
}

static void virtnet_rx_done(struct virtqueue *vq)
{
    struct virtio_net *vnet = vq->vdev->priv;

    API_SemaphoreMPend(vnet->vnet_lock, LW_OPTION_WAIT_INFINITE);

    virtqueue_disable_cb(vq);
    netdev_notify(&vnet->vnet_dev, LINK_INPUT, 0);
    virtqueue_enable_cb(vq);

    API_SemaphoreMPost(vnet->vnet_lock);
}

static int init_vq(struct virtio_net *vnet)
{
    int err;
    int i;
    vq_callback_t **callbacks;
    const char **names;
    struct virtqueue **vqs;
    unsigned short num_vqs;
    struct virtio_device *vdev = vnet->vdev;

    // tx & rx
    num_vqs = 2;

    // rx vqs[0], tx vqs[1], rx must be the first
    vnet->vqs = kmalloc(num_vqs * sizeof(*vnet->vqs), GFP_KERNEL);
    if (!vnet->vqs)
        return -ENOMEM;

    names = kmalloc(num_vqs * sizeof(*names), GFP_KERNEL);
    callbacks = kmalloc(num_vqs * sizeof(*callbacks), GFP_KERNEL);
    vqs = kmalloc(num_vqs * sizeof(*vqs), GFP_KERNEL);
    if (!names || !callbacks || !vqs) {
        err = -ENOMEM;
        goto out;
    }

    // rx
    callbacks[0] = virtnet_rx_done;
    snprintf(vnet->vqs[0].name, VQ_NAME_LEN, "req.%d", 0);
    names[0] = vnet->vqs[0].name;

    // tx
    callbacks[1] = virtnet_tx_done;
    snprintf(vnet->vqs[1].name, VQ_NAME_LEN, "req.%d", 1);
    names[1] = vnet->vqs[1].name;

    /* Discover virtqueues and write information to configuration.  */
    err = virtio_find_vqs(vdev, num_vqs, vqs, callbacks, names, NULL);
    if (err)
        goto out;

    for (i = 0; i < num_vqs; i++) {
        vnet->vqs[i].vq = vqs[i];
    }

    //setup tx & rx buffers
    vnet->rxbuffer = API_VmmDmaAllocAlign(VIRTIO_NET_NUM_RX_BUFS * VIRTIO_NET_RX_BUF_SIZE, PAGE_SIZE);
    vnet->txbuffer = API_VmmDmaAllocAlign(VIRTIO_NET_NUM_RX_BUFS * VIRTIO_NET_RX_BUF_SIZE, PAGE_SIZE);
    bzero(vnet->rxbuffer, VIRTIO_NET_NUM_RX_BUFS * VIRTIO_NET_RX_BUF_SIZE);
    bzero(vnet->txbuffer, VIRTIO_NET_NUM_RX_BUFS * VIRTIO_NET_RX_BUF_SIZE);

    // setup virtionet header
    vnet->req = API_VmmDmaAlloc(sizeof(struct virtnet_req));
    bzero(vnet->req, sizeof(struct virtnet_req));

out:
    kfree(vqs);
    kfree(callbacks);
    kfree(names);
    if (err)
        kfree(vnet->vqs);
    return err;
}

static int virtnet_init(struct netdev *pnetdev)
{
    struct virtio_net *vnet = (struct virtio_net *)pnetdev;
    struct scatterlist sg;
    struct scatterlist *sgs[] = { &sg };
    int i;
    bool notify = false;

    if (!vnet->rx_inited) {
        API_SemaphoreMPend(vnet->vnet_lock, LW_OPTION_WAIT_INFINITE);
        /* receive buffer length is always 1526 */
        sg.length = VIRTIO_NET_RX_BUF_SIZE;

        /* setup the receive buffer address */
        for (i = 0; i < VIRTIO_NET_NUM_RX_BUFS; i++) {
            sg.addr = vnet->rxbuffer + i * VIRTIO_NET_RX_BUF_SIZE;
            virtqueue_add_sgs(vnet->vqs[0].vq, sgs, 0, 1, sg.addr, 0);
        }

        if (virtqueue_kick_prepare(vnet->vqs[0].vq))
            notify = true;
        API_SemaphoreMPost(vnet->vnet_lock);

        if (notify)
            virtqueue_notify(vnet->vqs[0].vq);

        vnet->rx_inited = 1;
    }

    return 0;
}

static int virtnet_send(struct netdev *pnetdev, struct pbuf *p)
{
    const struct vring *vring = NULL;
    struct virtio_net *vnet = (struct virtio_net *)pnetdev;
    struct scatterlist hdr_sg;
    struct scatterlist data_sg;
    struct scatterlist *sgs[] = { &hdr_sg, &data_sg };
    void  *buf = NULL;
    u16    len;
    int    ret;
    bool notify = false;

    // check link state
    netdev_get_linkup(pnetdev, &ret);
    if (!ret) {
        netdev_linkinfo_err_inc(pnetdev);
        netdev_linkinfo_drop_inc(pnetdev);
        return -1;
    }

    API_SemaphoreMPend(vnet->vnet_lock, LW_OPTION_WAIT_INFINITE);

    // wait for post
    if (!vnet->vqs[1].vq->num_free) {
        API_SemaphoreMPost(vnet->vnet_lock);

        API_SemaphoreBPend(vnet->vnet_txpost, LW_OPTION_WAIT_INFINITE);
    }

    vring = virtqueue_get_vring(vnet->vqs[1].vq);

    buf = vnet->txbuffer + VIRTIO_NET_RX_BUF_SIZE * (vring->num - vnet->vqs[1].vq->num_free);
    len = p->tot_len;

    hdr_sg.addr   = vnet->req;
    hdr_sg.length = vnet->vnet_hdr_len;
    lib_bzero(hdr_sg.addr, hdr_sg.length);

    data_sg.addr   = buf;
    data_sg.length = len;
    pbuf_copy_partial(p, (void *)buf, len, 0);

    virtqueue_add_sgs(vnet->vqs[1].vq, sgs, 2, 0, data_sg.addr, 0);

    if (virtqueue_kick_prepare(vnet->vqs[1].vq))
        notify = true;

    netdev_statinfo_total_add(pnetdev, LINK_OUTPUT, len);
    if (1 == (((UINT8 *)p->payload)[1] & 0x1)) {
        netdev_statinfo_mcasts_inc(pnetdev, LINK_OUTPUT);
    } else {
        netdev_statinfo_ucasts_inc(pnetdev, LINK_OUTPUT);
    }

    API_SemaphoreMPost(vnet->vnet_lock);

    if (notify)
        virtqueue_notify(vnet->vqs[1].vq);

    return 0;
}

static int virtnet_free_pkt(struct virtqueue *vq, void *buf)
{
    struct scatterlist sg = { buf, VIRTIO_NET_RX_BUF_SIZE };
    struct scatterlist *sgs[] = { &sg };
    bool notify = false;

    /* Put the buffer back to the rx ring */
    virtqueue_add_sgs(vq, sgs, 0, 1, sg.addr, 0);

    if (virtqueue_kick_prepare(vq))
        notify = true;

    if (notify)
        virtqueue_notify(vq);

    return 0;
}

static void virtnet_recv(struct netdev *pnetdev, int(*input)(struct netdev *, struct pbuf *))
{
    struct virtio_net *vnet = (struct virtio_net *)pnetdev;
    struct pbuf *pbuf;
    void        *buf = NULL;
    u_int        len = 0;
    u8           type;

    do {
        buf = virtqueue_get_buf(vnet->vqs[0].vq, &len);
        if (!buf || len == 0) {
            goto handle_down;
        }

        buf = buf + vnet->vnet_hdr_len;

        pbuf = netdev_pbuf_alloc(len);
        if (LW_NULL == pbuf) {
            netdev_linkinfo_err_inc(pnetdev);
            netdev_statinfo_discards_inc(pnetdev, LINK_INPUT);
            goto return_pkt;
        }

        pbuf_take(pbuf, buf, len);

        type = ((UINT8 *)pbuf->payload)[0];
        if (input(pnetdev, pbuf) < 0) {
            netdev_pbuf_free(pbuf);
            netdev_statinfo_discards_inc(pnetdev, LINK_INPUT);
            goto return_pkt;
        }

        netdev_linkinfo_recv_inc(pnetdev);
        netdev_statinfo_total_add(pnetdev, LINK_INPUT, len);

        if (type) {
            netdev_statinfo_mcasts_inc(pnetdev, LINK_INPUT);
        } else {
            netdev_statinfo_ucasts_inc(pnetdev, LINK_INPUT);
        }

        virtnet_free_pkt(vnet->vqs[0].vq, buf);
    } while (1);

return_pkt:
    virtnet_free_pkt(vnet->vqs[0].vq, buf);
handle_down:
    return;
}

static int virtnet_rxmode(netdev_t *pnetdev, int flags)
{
    return 0;
}

static struct netdev_funcs vnet_drv_funcs = {
        .init     = virtnet_init,
        .receive  = virtnet_recv,
        .transmit = virtnet_send,
        .rxmode   = virtnet_rxmode,
};

static int virtnet_probe(struct virtio_device *vdev)
{
    struct virtio_net *vnet;
    netdev_t *pdev;
    int err;

    vdev->priv = vnet = sys_zalloc(sizeof(*vnet));
    vnet->vdev = vdev;
    pdev = &vnet->vnet_dev;

    vnet->vnet_txpost = API_SemaphoreBCreate("vnet_txsync", 0,
                                             LW_OPTION_INHERIT_PRIORITY |
                                             LW_OPTION_OBJECT_GLOBAL,
                                             LW_NULL);

    vnet->vnet_lock = API_SemaphoreMCreate("vnet_lock", LW_PRIO_DEF_CEILING,
                                           LW_OPTION_INHERIT_PRIORITY | LW_OPTION_OBJECT_GLOBAL,
                                           LW_NULL);

    if (virtio_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF) ||
        virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
        vnet->vnet_hdr_len = sizeof(struct virtio_net_hdr_mrg_rxbuf);
    else
        vnet->vnet_hdr_len = sizeof(struct virtio_net_hdr);

    err = init_vq(vnet);
    if (err) {
        _PrintFormat("init_vq fail!\r\n");
        return -1;
    }

    virtio_device_ready(vdev);

    // setup netdev_t
    pdev->magic_no = NETDEV_MAGIC;
    sprintf(pdev->dev_name, "virtnet_%d", 0);
    sprintf(pdev->if_name, "en");

    pdev->if_hostname = "SylixOS";
    pdev->init_flags  =  NETDEV_INIT_LOAD_PARAM   | \
                         NETDEV_INIT_LOAD_DNS     | \
                         NETDEV_INIT_IPV6_AUTOCFG | \
                         NETDEV_INIT_AS_DEFAULT;
    pdev->chksum_flags = NETDEV_CHKSUM_ENABLE_ALL;
    pdev->net_type     = NETDEV_TYPE_ETHERNET;
    pdev->speed        = 0;
    pdev->mtu          = 1500;
    pdev->hwaddr_len   = 6;
    pdev->drv          = &vnet_drv_funcs;

    // setup mac addr
    if (virtio_has_feature(vdev, VIRTIO_NET_F_MAC)) {
        virtio_cread_bytes(vdev,  offsetof(struct virtio_net_config, mac), pdev->hwaddr, sizeof(pdev->hwaddr));
    } else {
        pdev->hwaddr[0]    = 0x02;
        pdev->hwaddr[1]    = 0x81;
        pdev->hwaddr[2]    = 0x48;
        pdev->hwaddr[3]    = 0xfd;
        pdev->hwaddr[4]    = 0xde;
        pdev->hwaddr[5]    = 0x8c;
    }

    netdev_add(pdev, \
               "192.168.99.102", \
               "255.255.255.0", \
               "192.168.99.100", \
               IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST | IFF_PROMISC | IFF_ALLMULTI);

    netdev_set_linkup(pdev, 1, 1000 * 1000 * 1000);

    return 0;
}

static void virtnet_remove(struct virtio_device *vdev)
{
    /*
     *  TODO: do noyhing now
     */
}

static struct virtio_device_id id_table[] = {
    { VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID },
    { 0 },
};

#define VIRTNET_FEATURES \
    VIRTIO_NET_F_CSUM, VIRTIO_NET_F_GUEST_CSUM, \
    VIRTIO_NET_F_MAC, \
    VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_UFO, VIRTIO_NET_F_HOST_TSO6, \
    VIRTIO_NET_F_HOST_ECN, VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6, \
    VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_UFO, \
    VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_STATUS, VIRTIO_NET_F_CTRL_VQ, \
    VIRTIO_NET_F_CTRL_RX, VIRTIO_NET_F_CTRL_VLAN, \
    VIRTIO_NET_F_GUEST_ANNOUNCE, VIRTIO_NET_F_MQ, \
    VIRTIO_NET_F_CTRL_MAC_ADDR, \
    VIRTIO_NET_F_MTU, VIRTIO_NET_F_CTRL_GUEST_OFFLOADS, \
    VIRTIO_NET_F_SPEED_DUPLEX, VIRTIO_NET_F_STANDBY

static unsigned int features[] = {
    VIRTIO_NET_F_MAC,
    VIRTIO_F_NOTIFY_ON_EMPTY,
    VIRTIO_F_ANY_LAYOUT,
    VIRTIO_F_VERSION_1
};

static unsigned int features_legacy[] = {
    VIRTIO_NET_F_MAC,
    VIRTIO_F_NOTIFY_ON_EMPTY,
    VIRTIO_F_ANY_LAYOUT,
    VIRTIO_F_VERSION_1
};

static struct virtio_driver virtio_net_driver = {
    .feature_table = features,
    .feature_table_size = ARRAY_SIZE(features),
    .feature_table_legacy = features_legacy,
    .feature_table_size_legacy = ARRAY_SIZE(features_legacy),
    .id_table = id_table,
//    .validate = virtnet_validate,
    .probe =    virtnet_probe,
    .remove =   virtnet_remove,
//    .config_changed = virtnet_config_changed,
};

int virtio_net_driver_init(void)
{
    int ret = 0;

    ret = register_virtio_driver(&virtio_net_driver);
    return ret;
}
